#ifdef __cplusplus
extern "C"
{
#endif
    #include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif

#include <unistd.h>
#include "mediaplayer.h"
#include "audiotrack.h"

static MediaPlayer* sPlayer;

MediaPlayer::MediaPlayer() : mListener(NULL), mCookie(NULL),
 mDuration(0), mStreamType(MUSIC), mCurrentPosition(0), mSeekPosition(0),
 mCurrentState(MEDIA_PLAYER_IDLE), mPrepareSync(false), mPrepareStatus(NO_ERROR), mLoop(false),
 mRotate(0) {

    pthread_mutex_init(&mLock, NULL);
    mVideoWidth = mVideoHeight = 0;
    sPlayer = this;

    fmt_ctx = NULL;
    video_dec_ctx = NULL;

    video_dst_data[4] = {NULL};

    av_log_set_callback(ffmpegNotify);
}

MediaPlayer::~MediaPlayer() {
    pthread_mutex_destroy(&mLock);

    if (mListener != NULL) {
        free(mListener);
    }

    avcodec_free_context(&video_dec_ctx);
    avformat_close_input(&fmt_ctx);

    av_free(video_dst_data[0]);

    if (mDecoderVideo) {
        delete mDecoderVideo;
    }

}

status_t MediaPlayer::setDataSource(const char *url) {
    /* open input file, and allocate format context */
    if (avformat_open_input(&fmt_ctx, url, NULL, NULL) < 0) {
        LOGE("Could not open source file %s\n", url);
        return INVALID_OPERATION;
    }

    /* retrieve stream information */
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        LOGE("Could not find stream information\n");
        return INVALID_OPERATION;
    }
    LOGD("setDataSource(%s)", url);
    mCurrentState = MEDIA_PLAYER_INITIALIZED;
    return NO_ERROR;
}

status_t MediaPlayer::setListener(MediaPlayerListener* listener)
{
    LOGD("setListener");
    mListener = listener;
    return NO_ERROR;
}

status_t MediaPlayer::prepare() {
    status_t ret;
    mCurrentState = MEDIA_PLAYER_PREPARING;
    if ((ret = prepareVideo()) != NO_ERROR) {
        mCurrentState = MEDIA_PLAYER_STATE_ERROR;
        return ret;
    }

    mCurrentState = MEDIA_PLAYER_PREPARED;

    notify(MEDIA_PREPARED, mRotate, mVideoWidth, mVideoHeight);

    return NO_ERROR;
}

status_t MediaPlayer::start() {
    if (mCurrentState != MEDIA_PLAYER_PREPARED) {
        return INVALID_OPERATION;
    }
    pthread_create(&mPlayerThread, NULL, startPlayer, NULL);

//    decodeMovie(NULL);

    return NO_ERROR;
}

status_t MediaPlayer::pause()
{
    //pthread_mutex_lock(&mLock);
    mCurrentState = MEDIA_PLAYER_PAUSED;
    //pthread_mutex_unlock(&mLock);
    return NO_ERROR;
}

status_t MediaPlayer::resume() {
    mCurrentState = MEDIA_PLAYER_STARTED;

    return NO_ERROR;
}

bool MediaPlayer::isPlaying()
{
    return mCurrentState == MEDIA_PLAYER_STARTED ||
           mCurrentState == MEDIA_PLAYER_DECODED;
}

status_t MediaPlayer::stop() {
    //pthread_mutex_lock(&mLock);
    mCurrentState = MEDIA_PLAYER_STOPPED;
    //pthread_mutex_unlock(&mLock);

    if(mDecoderVideo != NULL) {
        mDecoderVideo->stop();
        mDecoderVideo->join();
    }

    if (pthread_join(mPlayerThread, NULL) != 0) {
        LOGD("Couldn't cancel player thread");
    }

    return NO_ERROR;
}

int MediaPlayer::getVideoWidth() {
    if (mCurrentState < MEDIA_PLAYER_PREPARED) {
        return 0;
    }
    return mVideoWidth;
}

int MediaPlayer::getVideoHeight() {
    if (mCurrentState < MEDIA_PLAYER_PREPARED) {
        return 0;
    }
    return mVideoHeight;
}

int64_t MediaPlayer::getCurrentPosition() {
    if (mCurrentState < MEDIA_PLAYER_PREPARED) {
        return 0;
    }
    /*av_gettime()*/;
    //__android_log_print(ANDROID_LOG_INFO, TAG, "position %i", *msec);
    return 0;
}

int64_t MediaPlayer::getDuration() {
    if (mCurrentState < MEDIA_PLAYER_PREPARED) {
        return 0;
    }

    return mDuration / 1000L;
}

void* MediaPlayer::startPlayer(void *ptr) {
    LOGD("starting main player thread");
    sPlayer->decodeMovie(ptr);
}

status_t MediaPlayer::seekTo(uint64_t msec)
{
    return INVALID_OPERATION;
}

status_t MediaPlayer::reset()
{
    return INVALID_OPERATION;
}

status_t MediaPlayer::prepareVideo() {
    LOGD("prepareVideo");

    int ret;
    AVStream *st;
    AVCodec *dec = NULL;
    AVDictionary *opts = NULL;

    AVMediaType type = AVMEDIA_TYPE_VIDEO;
    mVideoStreamIndex = -1;

    ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
    if (ret < 0) {
        LOGD( "Could not find %s stream\n",
              av_get_media_type_string(type));
        return INVALID_OPERATION;
    } else {
        mVideoStreamIndex = ret;
        st = fmt_ctx->streams[mVideoStreamIndex];

        AVDictionaryEntry *tag = NULL;

        while ((tag = av_dict_get(st->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
            LOGD("probe ----------------  %s=%s\n", tag->key, tag->value);
            if (strcmp("rotate", tag->key) == 0) {
                mRotate = atoi(tag->value);
            }
        }

        /* find decoder for the stream */
        dec = avcodec_find_decoder(st->codecpar->codec_id);
        if (!dec) {
            LOGD( "Failed to find %s codec\n",
                  av_get_media_type_string(type));
            return INVALID_OPERATION;
        }

        /* Allocate a codec context for the decoder */
        video_dec_ctx = avcodec_alloc_context3(dec);
        if (!video_dec_ctx) {
            LOGD( "Failed to allocate the %s codec context\n",
                  av_get_media_type_string(type));
            return INVALID_OPERATION;
        }

        /* Copy codec parameters from input stream to output codec context */
        if ((ret = avcodec_parameters_to_context(video_dec_ctx, st->codecpar)) < 0) {
            LOGD( "Failed to copy %s codec parameters to decoder context\n",
                  av_get_media_type_string(type));
            return INVALID_OPERATION;
        }

        if ((ret = avcodec_open2(video_dec_ctx, dec, &opts)) < 0) {
            LOGD( "Failed to open %s codec\n",
                  av_get_media_type_string(type));
            return INVALID_OPERATION;
        }

        mVideoWidth = video_dec_ctx->width;
        mVideoHeight = video_dec_ctx->height;
        mDuration =  fmt_ctx->duration;

        video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize,
                                 mVideoWidth, mVideoHeight, AV_PIX_FMT_YUV420P, 1);

        if (video_dst_bufsize < 0) {
            LOGE("Could not allocate raw video buffer\n");
            return INVALID_OPERATION;
        }

        return NO_ERROR;
    }
}

void MediaPlayer::decodeMovie(void *ptr){
    AVPacket pPacket;
    av_init_packet(&pPacket);
    pPacket.data = NULL;
    pPacket.size = 0;

    AVStream* stream_video = fmt_ctx->streams[mVideoStreamIndex];
    stream_video->codec = video_dec_ctx;
    mDecoderVideo = new DecoderVideo(stream_video);
    mDecoderVideo->onDecode = decode;
    mDecoderVideo->start();

    mCurrentState = MEDIA_PLAYER_STARTED;
    LOGD("playing %ix%i", mVideoWidth, mVideoHeight);

    int count = 0;

    while (mCurrentState != MEDIA_PLAYER_DECODED && mCurrentState != MEDIA_PLAYER_STOPPED &&
           mCurrentState != MEDIA_PLAYER_STATE_ERROR)
    {
        if (mDecoderVideo->packets() > FFMPEG_PLAYER_MAX_QUEUE_SIZE) {
            usleep(1000 * 20);
            continue;
        }

        if (av_read_frame(fmt_ctx, &pPacket) < 0) {
            mCurrentState = MEDIA_PLAYER_DECODED;
            continue;
        }


        if (pPacket.stream_index == mVideoStreamIndex) {
            LOGD("av_read_frame -------#######################------- size=%d, count=%d", pPacket.size, count);
            count++;
            mDecoderVideo->enqueue(&pPacket);
            av_packet_unref(&pPacket);

//            usleep(1000 * 30);
        } /*else if (pPacket.stream_index == mAudioStreamIndex) {

        } */else {
            // Free the packet that was allocated by av_read_frame
            av_packet_unref(&pPacket);
        }
    }

//    LOGD("waiting on video thread");
//    int ret = -1;
//    if ((ret = mDecoderVideo->join()) != 0) {
//        LOGD("Couldn't cancel video thread: %i", ret);
//    }

    if(mCurrentState == MEDIA_PLAYER_STATE_ERROR) {
        LOGD("playing err");
    }

    mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE;
}

void MediaPlayer::setDecodeCallback(VideoOnDecoded decoded) {
    mOnDecoded = decoded;
}

void MediaPlayer::decode(AVFrame *frame, double pts) {
    //接收解码后的frame
//    av_image_copy(sPlayer->video_dst_data, sPlayer->video_dst_linesize,
//                  (const uint8_t **) (frame->data), frame->linesize,
//                  AV_PIX_FMT_YUV420P, frame->width, frame->height);
//
//    LOGD("MediaPlayer decode width:%d, height:%d\n", frame->width, frame->height);

    sPlayer->mOnDecoded(frame, pts, sPlayer->mVideoWidth , sPlayer->mVideoHeight);

}

bool MediaPlayer::shouldCancel(PacketQueue* queue)
{
    return (mCurrentState == MEDIA_PLAYER_STATE_ERROR || mCurrentState == MEDIA_PLAYER_STOPPED ||
            ((mCurrentState == MEDIA_PLAYER_DECODED || mCurrentState == MEDIA_PLAYER_STARTED)
             && queue->size() == 0));
}

void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl) {

    switch(level) {
        /**
         * Something went really wrong and we will crash now.
         */
        case AV_LOG_PANIC:
            LOGE("AV_LOG_PANIC: %s", fmt);
            //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR;
            break;

            /**
             * Something went wrong and recovery is not possible.
             * For example, no header was found for a format which depends
             * on headers or an illegal combination of parameters is used.
             */
        case AV_LOG_FATAL:
            LOGE("AV_LOG_FATAL: %s", fmt);
            //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR;
            break;

            /**
             * Something went wrong and cannot losslessly be recovered.
             * However, not all future data is affected.
             */
        case AV_LOG_ERROR:
            LOGE("AV_LOG_ERROR: %s", fmt);
            //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR;
            break;

            /**
             * Something somehow does not look correct. This may or may not
             * lead to problems. An example would be the use of '-vstrict -2'.
             */
        case AV_LOG_WARNING:
            LOGE("AV_LOG_WARNING: %s", fmt);
            break;

        case AV_LOG_INFO:
            LOGI("%s", fmt);
            break;

        case AV_LOG_DEBUG:
            LOGD("%s", fmt);
            break;

        default:
            LOGD("%s", fmt);
            break;

    }
}

void MediaPlayer::notify(int msg, int ext1, int ext2, int ext3)
{
    //__android_log_print(ANDROID_LOG_INFO, TAG, "message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
    bool send = true;
    bool locked = false;

    if (mListener != NULL) {
        mListener->notify(msg, ext1, ext2, ext3);
    }
}